#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/euler_angles.hpp>

using namespace glm;

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <vector>

using namespace std;

void error(const char* errorMessage){
	printf("error : %s \n",errorMessage);
	exit(1);
}

#pragma pack (push,1)

	class Vertex {
	public:
	
		vec3 position;
	
		vec3 normal;
	
		vec2 texcoord;
	
		int boneId;
	
	};

	class MS3DHeader {
	public:

		char id[10];

		int version;

		void load(FILE* f){
			if (fread(this,sizeof(MS3DHeader),1,f)!=1)
				error("uninspected end of file");
			if (memcmp(id,"MS3D000000",10))
				error("not ms3d file");
			if (version!=4)
				error("wrong file version");
		}
	
	} header;
	
	class MS3DVertex {
	public:
	
		char flags; 
		
		float vertex[3];
		
		char boneId; // -1 = no bone
    	
    	char referenceCount;		
	
		void load(FILE* f){
			if (fread(this,sizeof(MS3DVertex),1,f)!=1)
				error("uninspected end of file");
		}
		
	};
	
	class MS3DVertexVector : public vector <MS3DVertex> {
	public:
	
		void load(FILE* f){
			clear();
			short count;
			if (fread(&count,sizeof(count),1,f)!=1)
				error("uninspected end of file");
			printf("vertex count : %d\n",count);
				resize(count);
			for (int i = 0;i<count;i++)
				(*this)[i].load(f);
		}

	} vertex;
	
	class MS3DTriangle {
	public:
	
		short flags;
		
		short vertexIndices[3];
		
		float vertexNormals[3][3];
		
		float s[3];
		
		float t[3];
		
		char smoothingGroup; // 1 - 32
		
		char groupIndex;
		
		void load(FILE* f){
			if (fread(this,sizeof(MS3DTriangle),1,f)!=1)
				error("uninspected end of file");
		}
		
	};

	class MS3DTriangleVector : public vector <MS3DTriangle> {
	public:
	
		void load(FILE* f){
			clear();
			short count;
			if (fread(&count,sizeof(count),1,f)!=1)
				error("uninspected end of file");
			printf("triangle count : %d\n",count);
				resize(count);
			for (int i = 0;i<count;i++)
				(*this)[i].load(f);
		}

	} triangle;
	
	class MS3DGroup {
	public:
	
		char flags;
		
		char name[32];
		
		unsigned short numtriangles;
		
		vector <unsigned short> triangleIndices; // the groups group the triangles
		
		char materialIndex; // -1 = no material	
	
		void load(FILE* f){
			if (fread(this,35,1,f)!=1)
				error("uninspected end of file");
			printf("group : %s\n",name);
			triangleIndices.resize(numtriangles);
			if (fread(triangleIndices.data(),2,numtriangles,f)!=numtriangles)
				error("uninspected end of file");
			if (fread(&materialIndex,1,1,f)!=1)
				error("uninspected end of file");
		}
		
	};

	class MS3DGroupVector : public vector <MS3DGroup> {
	public:
	
		void load(FILE* f){
			clear();
			short count;
			if (fread(&count,sizeof(count),1,f)!=1)
				error("uninspected end of file");
			printf("group count : %d\n",count);
				resize(count);
			for (int i = 0;i<count;i++)
				(*this)[i].load(f);
		}

	} group;
	
	class MS3DMaterial {
	public:
		
		char name[32];
		
		float ambient[4];
		
		float diffuse[4];
		
		float specular[4];
		
		float emissive[4];
		
		float shininess; // 0.0f - 128.0f
		
		float transparency; // 0.0f - 1.0f
		
		char mode; // 0, 1, 2 is unused now
		
		char texture[128];
		
		char alphamap[128];
		
		void load (FILE* f){
			if (fread(this,sizeof(MS3DMaterial),1,f)!=1)
				error("uninspected end of file");
			printf("material : %s\n",name);
		}
		
	};

	class MS3DMaterialVector : public vector <MS3DMaterial> {
	public:
	
		void load(FILE* f){
			clear();
			short count;
			if (fread(&count,sizeof(count),1,f)!=1)
				error("uninspected end of file");
			printf("material count : %d\n",count);
				resize(count);
			for (int i = 0;i<count;i++)
				(*this)[i].load(f);
		}

	} material;
	
	float fAnimationFPS; // 4 bytes

	float fCurrentTime; // 4 bytes

	int iTotalFrames; // 4 bytes
	
	class MS3DJoint {
	public:
	
		struct Key{
		
			float time; // time in seconds
			
			float value[3]; // x, y, z angles
			
		};	
	
		char flags;
		
		char name[32];
		
		char parentName[32];
		
		float rotation[3]; // local reference matrix
		
		float position[3];
		
		unsigned short numKeyFramesRot;
		
		unsigned short numKeyFramesTrans;
		
		vector <Key> keyFramesRot;

		vector <Key> keyFramesTrans;
		
		mat4 absoluteMatrix;
		
		mat4 relativeMatrix;
		
		int parentIndex;
		
		void load(FILE* f){
			if (fread(this,93,1,f)!=1)
				error("uninspected end of file");
			keyFramesRot.resize(numKeyFramesRot);
			if (fread(keyFramesRot.data(),sizeof(Key),numKeyFramesRot,f)!=numKeyFramesRot)
				error("uninspected end of file");
			keyFramesTrans.resize(numKeyFramesTrans);
			if (fread(keyFramesTrans.data(),sizeof(Key),numKeyFramesTrans,f)!=numKeyFramesTrans)
				error("uninspected end of file");
		}
		
	};
	
	class MS3DJointVector : public vector <MS3DJoint> {
	public:
	
		void load(FILE* f){
			clear();
			short count;
			if (fread(&count,sizeof(count),1,f)!=1)
				error("uninspected end of file");
			printf("joint count : %d\n",count);
			resize(count);
			for (int i = 0;i<count;i++){
				MS3DJoint* jn = &(*this)[i];
				// загружаем
				jn->load(f);
//				printf("%s\n",jn->name);
				// определяем индекс предка
				jn->parentIndex = -1;
				for( int j = 0; j < i; j++){
					if (!strcmp((*this)[j].name,jn->parentName)) {
						jn->parentIndex = j;
//						printf("connected to : %s\n",(*this)[j].name);
						break;
					}
				}
				// расчет матриц
				jn->relativeMatrix = mat4(
					1,0,0,0,
					0,1,0,0,
					0,0,1,0,
					jn->position[0],jn->position[1],jn->position[2],1
				);
				jn->relativeMatrix = yawPitchRoll(
					jn->rotation[1],
					jn->rotation[0],
					jn->rotation[2]
				) * jn->relativeMatrix;
				// расчет обратной матрицы (абсолютной)
				jn->absoluteMatrix = inverse(jn->relativeMatrix);
				if (jn->parentIndex>-1)
					jn->absoluteMatrix = (*this)[jn->parentIndex].absoluteMatrix * jn->absoluteMatrix;
			}
		}

	} joint;	

#pragma pack (pop)

int main(int argc, char** argv){
	for (int i = 1; i<argc; i++){
		if (!strcmp(argv[i],"--help")){
			printf("ms3d-explode v0.01\n");
			printf("use : ms3d-explode <ms3d file name>\n");
		} else {
			printf("job : explode %s\n",argv[i]);
			// читаем файл
			FILE* f = fopen(argv[i],"rb");
			if (!f) {
				printf("file not found\n");
				continue;
			}
			
			char* buffer = (char*) malloc (strlen(argv[i])+1);
			strcpy(buffer,argv[i]);
			char* path = buffer;
			char* fname = buffer;
			while(*path){
				if (*path == '/')
					fname = path+1;
				path++;
			}
			if (fname != buffer)
				*(buffer-1) = 0;
			printf("path : %s\n",path);
			printf("fname : %s\n",fname);
			
			header.load(f);
			vertex.load(f);
			triangle.load(f);
			group.load(f);
			material.load(f);
			if (fread(&fAnimationFPS,sizeof(fAnimationFPS),1,f)!=1)
				error("uninspected end of file");
			if (fread(&fCurrentTime,sizeof(fCurrentTime),1,f)!=1)
				error("uninspected end of file");
			if (fread(&iTotalFrames,sizeof(iTotalFrames),1,f)!=1)
				error("uninspected end of file");
			printf("Animation FPS : %f\n",fAnimationFPS);
			printf("Current time : %f\n",fCurrentTime);
			printf("Total frames : %d\n",iTotalFrames);
			joint.load(f);
			// сохраняем группы как отдельные файлы
			printf("saving data:\n");
			for (auto git = group.begin(); git!= group.end(); ++git){
				char fileName[64];
				strcpy(fileName,(*git).name);
				strcat(fileName,".mesh");
				printf("%s\n",fileName);
				FILE* f=fopen(fileName,"wb");
				if (f) {
					// сохраняем меши
					vector <Vertex> vertexData;
					vertexData.reserve((*git).numtriangles*3);
					for (int t = 0; t != (*git).numtriangles; ++t){
						MS3DTriangle* tri = &triangle[(*git).triangleIndices[t]];
						for (int i = 0; i!=3; i++){
							Vertex v;
							int vIdx = tri->vertexIndices[i];
							v.position = vec3(vertex[vIdx].vertex[0],vertex[vIdx].vertex[1],vertex[vIdx].vertex[2]);
							v.normal = vec3(tri->vertexNormals[i][0],tri->vertexNormals[i][1],tri->vertexNormals[i][2]);
							v.boneId = vertex[vIdx].boneId;
							if (v.boneId > -1){
								vec4 p = joint[v.boneId].absoluteMatrix * vec4(v.position, 1);
								v.position = vec3(p.x, p.y, p.z) * p.w;
								p = joint[v.boneId].absoluteMatrix * vec4(v.normal, 1);
								v.normal = vec3(p.x, p.y, p.z) * p.w;
							}
							v.texcoord = vec2(tri->s[i],1.0-tri->t[i]);
							vertexData.push_back(v);
						}
					}
					unsigned int levelCount = 1;
					fwrite(&levelCount,sizeof(levelCount),1,f);
					float levelDistance = 0.0f;
					fwrite(&levelDistance,sizeof(levelDistance),1,f);
					unsigned int vertexCount = vertexData.size();
					fwrite(&vertexCount,sizeof(vertexCount),1,f);
					fwrite(vertexData.data(),sizeof(Vertex),vertexCount,f);
					fclose(f);
				} else {
					printf("can't create file %s\n",fileName);
					exit(1);
				} 
			}

			fclose(f);
			/* получаем имя файла без расширения */
			char* p = fname;
			while(*p) p++;
			while(p!=fname){
				if (*p=='.'){
					*p = 0;
					break;
				}
				p--;
			}
			
			// Формируем имя файла скелета
			char* skbuffer = (char*) malloc(strlen(fname)+10);
			strcpy(skbuffer,fname);
			strcat(skbuffer,".skel");
			printf("%s\n",skbuffer);
			
			//Сохраняем скелет
			f = fopen(skbuffer,"wb");
			unsigned int count = joint.size();
			fwrite(&count, sizeof(count), 1, f);
			for (unsigned int i = 0; i < joint.size(); ++i){
				fwrite(glm::value_ptr(joint[i].relativeMatrix),sizeof(float)*16,1,f);
				fwrite(&joint[i].parentIndex,sizeof(int),1,f);
			}
			free(skbuffer);
			
			printf("\n");
			free(buffer);
			fclose(f);
		}
		
	}
}
